package com.ihr360.job.core.repository.dao;

import com.ihr360.job.core.entity.StepExecutionLog;
import com.ihr360.job.core.repository.PageData;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.sql.Types;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class JdbcStepExecutionLogDao extends AbstractJdbcBatchMetadataDao implements StepExecutionLogDao {

    private static final String SAVE_LOG = "INSERT into %PREFIX%STEP_EXECUTION_LOG(JOB_EXECUTION_ID, STEP_EXECUTION_ID, LOG_TITLE_KEY, LOG_TITLE, LOG_CONTENT_KEY, LOG_CONTENT, LOG_LEVEL, LOG_TIME)"
            + " values (?, ?, ?, ?,?,?,?,now())";

    private static final String LOAD_FIRST_PAGE_LOG_ALL = "SELECT STEP_EXECUTION_LOG_ID as id,JOB_EXECUTION_ID as jobExecutionId,STEP_EXECUTION_ID as stepExecutionId,LOG_TITLE_KEY as logTitleKey, " +
            "LOG_TITLE as logTitle, LOG_CONTENT_KEY as logContentKey, LOG_CONTENT as logContent,LOG_LEVEL as logLevel, LOG_TIME as logTime " +
            "FROM %PREFIX%STEP_EXECUTION_LOG WHERE JOB_EXECUTION_ID IN(?) AND STEP_EXECUTION_LOG_ID>? order by STEP_EXECUTION_LOG_ID desc limit %PAGESIZE%";

    private static final String LOAD_FIRST_PAGE_LOG_ONLY_ERROR = "SELECT STEP_EXECUTION_LOG_ID as id,JOB_EXECUTION_ID as jobExecutionId,STEP_EXECUTION_ID as stepExecutionId,LOG_TITLE_KEY as logTitleKey, " +
            "LOG_TITLE as logTitle, LOG_CONTENT_KEY as logContentKey, LOG_CONTENT as logContent,LOG_LEVEL as logLevel, LOG_TIME as logTime " +
            "FROM %PREFIX%STEP_EXECUTION_LOG WHERE LOG_LEVEL=\"ERROR\" AND JOB_EXECUTION_ID IN(%IN%) AND STEP_EXECUTION_LOG_ID>? order by STEP_EXECUTION_LOG_ID desc limit %PAGESIZE%";

    private static final String LOAD_FIRST_PAGE_LOG_NO_TRACE = "SELECT STEP_EXECUTION_LOG_ID as id,JOB_EXECUTION_ID as jobExecutionId,STEP_EXECUTION_ID as stepExecutionId,LOG_TITLE_KEY as logTitleKey, " +
            "LOG_TITLE as logTitle, LOG_CONTENT_KEY as logContentKey, LOG_CONTENT as logContent,LOG_LEVEL as logLevel, LOG_TIME as logTime " +
            "FROM %PREFIX%STEP_EXECUTION_LOG WHERE LOG_LEVEL in (\"INFO\",\"WARNING\",\"ERROR\") AND JOB_EXECUTION_ID IN(%IN%) AND STEP_EXECUTION_LOG_ID>? order by STEP_EXECUTION_LOG_ID desc limit %PAGESIZE%";

    private static final String LOAD_PAGE_DATA_ALL = "SELECT STEP_EXECUTION_LOG_ID as id,JOB_EXECUTION_ID as jobExecutionId,STEP_EXECUTION_ID as stepExecutionId,LOG_TITLE_KEY as logTitleKey, " +
            "LOG_TITLE as logTitle, LOG_CONTENT_KEY as logContentKey, LOG_CONTENT as logContent,LOG_LEVEL as logLevel, LOG_TIME as logTime  " +
            "FROM %PREFIX%STEP_EXECUTION_LOG WHERE JOB_EXECUTION_ID IN(%IN%) AND STEP_EXECUTION_LOG_ID<? order by STEP_EXECUTION_LOG_ID desc limit %PAGESIZE%";

    private static final String LOAD_PAGE_DATA__ONLY_ERROR = "SELECT STEP_EXECUTION_LOG_ID as id,JOB_EXECUTION_ID as jobExecutionId,STEP_EXECUTION_ID as stepExecutionId,LOG_TITLE_KEY as logTitleKey, " +
            "LOG_TITLE as logTitle, LOG_CONTENT_KEY as logContentKey, LOG_CONTENT as logContent,LOG_LEVEL as logLevel, LOG_TIME as logTime  " +
            "FROM %PREFIX%STEP_EXECUTION_LOG WHERE LOG_LEVEL=\"ERROR\" AND JOB_EXECUTION_ID IN(%IN%) AND STEP_EXECUTION_LOG_ID<? order by STEP_EXECUTION_LOG_ID desc limit %PAGESIZE%";

    private static final String LOAD_PAGE_DATA_NO_TRACE = "SELECT STEP_EXECUTION_LOG_ID as id,JOB_EXECUTION_ID as jobExecutionId,STEP_EXECUTION_ID as stepExecutionId,LOG_TITLE_KEY as logTitleKey, " +
            "LOG_TITLE as logTitle, LOG_CONTENT_KEY as logContentKey, LOG_CONTENT as logContent,LOG_LEVEL as logLevel, LOG_TIME as logTime  " +
            "FROM %PREFIX%STEP_EXECUTION_LOG WHERE LOG_LEVEL in (\"INFO\",\"WARNING\",\"ERROR\") AND JOB_EXECUTION_ID IN(%IN%) AND STEP_EXECUTION_LOG_ID<? order by STEP_EXECUTION_LOG_ID desc limit %PAGESIZE%";

    private static final String CNT_QUERY = "SELECT COUNT(1) FROM %PREFIX%STEP_EXECUTION_LOG WHERE JOB_EXECUTION_ID IN(%IN%)";
    private static final String LOG_CNT_WARNING = "SELECT COUNT(1) FROM %PREFIX%STEP_EXECUTION_LOG WHERE LOG_LEVEL=\"WARNING\" AND JOB_EXECUTION_ID IN(%IN%)";
    private static final String LOG_CNT_ERROR = "SELECT COUNT(1) FROM %PREFIX%STEP_EXECUTION_LOG WHERE LOG_LEVEL=\"ERROR\" AND JOB_EXECUTION_ID IN(%IN%)";

    @Override
    public void log(StepExecutionLog log) {
        Assert.notNull(log, "log instance not be null.");
        Assert.notNull(log.getJobExecutionId(), "Job execution Id must not be null.");
        //Assert.notNull(log.getStepExecutionId(), "Step execution Id must not be null.");
        Assert.notNull(log.getLogLevel(), "Log level must be set");
        Assert.notNull(log.getLogContent(), "Log content must not be null");
        Object[] parameters = new Object[]{
                log.getJobExecutionId(),
                log.getStepExecutionId(),
                log.getLogTitleKey(),
                log.getLogTitle(),
                log.getLogContentKey(),
                log.getLogContent(),
                log.getLogLevel().toString()
        };
        getJdbcTemplate().update(getQuery(SAVE_LOG),
                parameters,
                new int[]{
                        Types.BIGINT,
                        Types.BIGINT,
                        Types.VARCHAR,
                        Types.VARCHAR,
                        Types.VARCHAR,
                        Types.VARCHAR,
                        Types.VARCHAR
                }
        );
    }

    @Override
    public void log(List<StepExecutionLog> logs) {
        List<Object[]> parametersList = logs.stream().map(log ->
                new Object[]{
                        log.getJobExecutionId(),
                        log.getStepExecutionId(),
                        log.getLogTitleKey(),
                        log.getLogTitle(),
                        log.getLogContentKey(),
                        log.getLogContent(),
                        log.getLogLevel().toString()
                }).collect(Collectors.toList());

        getJdbcTemplate().batchUpdate(getQuery(SAVE_LOG),
                parametersList,
                new int[]{
                        Types.BIGINT,
                        Types.BIGINT,
                        Types.VARCHAR,
                        Types.VARCHAR,
                        Types.VARCHAR,
                        Types.VARCHAR,
                        Types.VARCHAR
                }
        );
    }

    @Override
    public Long getErrorLogCount(List<Long> executionId) {
        String arrayStr = StringUtils.arrayToCommaDelimitedString(executionId.toArray());
        return queryCnt(LOG_CNT_ERROR, arrayStr);
    }

    @Override
    public Long getWarningLogCount(List<Long> executionId) {
        String arrayStr = StringUtils.arrayToCommaDelimitedString(executionId.toArray());
        return queryCnt(LOG_CNT_WARNING, arrayStr);
    }

    @Override
    public PageData<StepExecutionLog> loadLastestFirstPageLogs(List<Long> executionId, int pageSize, long lastLogId) {
        return getLastestLogs(executionId, pageSize, lastLogId, LOAD_FIRST_PAGE_LOG_ALL);
    }

    @Override
    public PageData<StepExecutionLog> loadLastestFirstPageLogsNoTrace(List<Long> executionId, int pageSize, long lastLogId) {
        return getLastestLogs(executionId, pageSize, lastLogId, LOAD_FIRST_PAGE_LOG_NO_TRACE);
    }

    @Override
    public PageData<StepExecutionLog> loadLastestFirstPageLogsOnlyError(List<Long> executionId, int pageSize, long lastLogId) {
        return getLastestLogs(executionId, pageSize, lastLogId, LOAD_FIRST_PAGE_LOG_ONLY_ERROR);
    }

    private PageData<StepExecutionLog> getLastestLogs(List<Long> executionId, int pageSize, long lastLogId, String baseSql) {
        if (org.springframework.util.CollectionUtils.isEmpty(executionId)) {
            return new PageData<>(0, 0, Collections.emptyList(), true);
        }
        String arrayStr = StringUtils.arrayToCommaDelimitedString(executionId.toArray());
        Long cnt = queryCnt(CNT_QUERY, arrayStr);
        List<StepExecutionLog> data = getJdbcTemplate().query(getQuery(baseSql, arrayStr, "" + pageSize), new Object[]{lastLogId}, new BeanPropertyRowMapper<>(StepExecutionLog.class));
        long pageCnt = cnt % pageSize != 0 ? cnt / pageSize + 1 : cnt / pageSize;
        return new PageData<>(pageCnt, cnt, data, false);
    }

    @Override
    public PageData<StepExecutionLog> loadLogs(List<Long> executionId, int pageSize, long lastLogId) {
        return getLogs(executionId, pageSize, lastLogId, LOAD_PAGE_DATA_ALL);
    }

    @Override
    public PageData<StepExecutionLog> loadLogsNoTrace(List<Long> executionId, int pageSize, long lastLogId) {
        return getLogs(executionId, pageSize, lastLogId, LOAD_PAGE_DATA_NO_TRACE);
    }

    @Override
    public PageData<StepExecutionLog> loadLogsOnlyError(List<Long> executionId, int pageSize, long lastLogId) {
        return getLogs(executionId, pageSize, lastLogId, LOAD_PAGE_DATA__ONLY_ERROR);
    }

    private PageData<StepExecutionLog> getLogs(List<Long> executionId, int pageSize, long lastLogId, String baseSql) {
        if (org.springframework.util.CollectionUtils.isEmpty(executionId)) {
            return new PageData<>(0, 0, Collections.emptyList(), true);
        }
        String arrayStr = StringUtils.arrayToCommaDelimitedString(executionId.toArray());
        Long cnt = queryCnt(CNT_QUERY, arrayStr);
        List<StepExecutionLog> data = getJdbcTemplate().query(getQuery(baseSql, arrayStr, "" + pageSize), new Object[]{lastLogId}, new BeanPropertyRowMapper<>(StepExecutionLog.class));
        long pageCnt = cnt % pageSize != 0 ? cnt / pageSize + 1 : cnt / pageSize;
        return new PageData<>(pageCnt, cnt, data, CollectionUtils.isEmpty(data));
    }

    private Long queryCnt(String baseSql, String executionIds) {
        return getJdbcTemplate().queryForObject(getQuery(baseSql, executionIds), new Object[]{}, Long.class);
    }
}